(for v0.4) Add a way to jump to bootloader and/or reset settings on startup#3213
(for v0.4) Add a way to jump to bootloader and/or reset settings on startup#3213rianadon wants to merge 12 commits intozmkfirmware:mainfrom
Conversation
Moved the logic to select the type of reboot from behavior_reset.c to a new zmk_reset() function. This allows rebooting to bootloader from other code, and it gives us a starting point for future work to support other bootloaders aside from the Adafruit nrf52 bootloader.
For now, this just clears BLE bonds, but it can be updated in the future to handle clearing all settings.
This adds an optional feature to trigger an action if a specific key is held when the keyboard is powered on. It can be configured to jump to the bootloader and/or clear settings. This is inspired by QMK's "bootmagic lite" feature, and it is primarily intended as a way to recover a keyboard which doesn't have a physical reset button in case it is flashed with firmware that doesn't have a &bootloader key in its keymap. It can also be used to clear BLE bonds on the peripheral side of a split keyboard without needing to flash special firmware.
petejohanson
left a comment
There was a problem hiding this comment.
Thanks for working on this! A few thoughts on the implementation.
| jump-to-bootloader: | ||
| type: boolean | ||
| description: Reboots into the bootloader. | ||
| reset-settings: | ||
| type: boolean | ||
| description: Clears settings and reboots. |
There was a problem hiding this comment.
It would be really nice, now that we have ZMK Studio, if we could also add an option here for doing a "restore stock keymap", just in case someone mistakenly removes their &studio_unlock from their keymap when making changes.
On that node, this should probably just be one properly, that's an enum, e.g.:
| jump-to-bootloader: | |
| type: boolean | |
| description: Reboots into the bootloader. | |
| reset-settings: | |
| type: boolean | |
| description: Clears settings and reboots. | |
| action: | |
| type: string | |
| required: true | |
| enum: | |
| - "jump-to-bootloader" | |
| - "reset-settings" | |
| - "restore-studio-stock-keymap" |
since it only makes sense, IMHO, for a given boot magic key to perform one of the possible boot magic actions, and allows us to grow the enum list later.
There was a problem hiding this comment.
+1 on the ZMK Studio "restore default keymap" idea - worth logging on https://github.com/zmkfirmware/zmk-studio/issues ?
There was a problem hiding this comment.
I think it would be even nicer if, instead of restoring the default keymap, we had an option to just unlock studio.
There was a problem hiding this comment.
I think it would be even nicer if, instead of restoring the default keymap, we had an option to just unlock studio.
Yeah, like this much better.
There was a problem hiding this comment.
It's almost sounding like the action should just be a pointer to some ZMK behavior :)
|
|
||
| // Hardcoding a reasonable hardcoded value of peripheral addresses | ||
| // to clear so we properly clear a split central as well. | ||
| for (int i = 0; i < 8; i++) { |
There was a problem hiding this comment.
Given this and the loop above both iterate 8 times, lets unify this into one loop that deletes both kinds of settings.
| @@ -0,0 +1,94 @@ | |||
| /* | |||
There was a problem hiding this comment.
I'm tempted to say this code should live in the fairly recently created app/src/boot/ directory at this point.
| @@ -0,0 +1,94 @@ | |||
| /* | |||
| * Copyright (c) 2023 The ZMK Contributors | |||
There was a problem hiding this comment.
| * Copyright (c) 2023 The ZMK Contributors | |
| * Copyright (c) 2026 The ZMK Contributors |
| zmk_reset_settings(); | ||
| } | ||
|
|
||
| if (config->jump_to_bootloader) { |
There was a problem hiding this comment.
Maybe I need a refresher here, or @joelspadin can weigh in... But do we really have a use case where you want to reset the settings and then jump to the bootloader? Having a hard time imaging a real need for that specific use case.
There was a problem hiding this comment.
I'm sure I probably came up with a reason for that when I originally wrote this, but I can't think of one now.
There was a problem hiding this comment.
QMK does a reset then jump, but it's always bothered me that there's no way to do just a jump to bootloader. With QMK I've had some troubles with my saved changes causing conflicts when I make major changes to the hardware like changing the number of keys in the firmware, and a settings reset was necessary. I haven't stress tested ZMK as much, but if this isn't an issue here (Studio does seem very well architected), then I agree just one makes sense.
| @@ -0,0 +1,63 @@ | |||
| /* | |||
| * Copyright (c) 2023 The ZMK Contributors | |||
There was a problem hiding this comment.
| * Copyright (c) 2023 The ZMK Contributors | |
| * Copyright (c) 2026 The ZMK Contributors |
|
|
||
| :::caution | ||
|
|
||
| Currently this action _only_ clears BLE bonds. It will be updated to reset all settings in the future. |
There was a problem hiding this comment.
If we're really going to have this only do this, I'm wary about this changing out from under folks in the future. Can we name this current action "clear-ble-bonds" or something, and add a true "reset-settings" action later as an additional action option, instead of the current PR approach?
@joelspadin any concerns?
There was a problem hiding this comment.
IIRC, I originally wrote this when the settings_reset shield only cleared BLE bonds and didn't reset all settings. I'd suggest starting with a full settings reset option. I'm not sure if there are cases where you'd want to clear BLE bonds only instead of resetting everything, but if so, we could add a separate option for that too.
There was a problem hiding this comment.
The main reason I can think to maintain a "clear BLE only" is to preserve any studio changes when trying to fix pairing issues between halves of a split, so I think there's a use case for both actions being possible from boot keys.
There was a problem hiding this comment.
While we are thinking about the options, I think it might be nice to have separate options for "clear split bonds" vs. "clear BLE profiles" (except the split ones). Not necessarily in the first iteration.
| If you want a single boot magic key to perform multiple actions, simply add properties for each action to the same `zmk,boot-magic-key` node. The order of the properties does not matter. | ||
|
|
||
| For example, to make a key that resets settings and then reboots to the bootloader, add both `reset-settings` and `jump-to-bootloader`: |
There was a problem hiding this comment.
Still not sure we really need this. Open to being convinced otherwise though. Thanks.
There was a problem hiding this comment.
Clean slate when you've been tinkering with ZMK studio while getting the matrix right? Saves building and flashing a settings reset firmware too.
There was a problem hiding this comment.
I mean a dual function key that both resets settings and jumps into the bootloader.... Seems super niche, and I'm not sure I understand your described need for studio iteration... Can you explain in exact steps what use case you're considering?
There was a problem hiding this comment.
Possibly user ignorance or misunderstanding, but I've flashed new firware onto a board to find what I though were traces of past changes I'd made in ZMK Studio. An easy way to flash new firmware and wipe out any past Studio changes seems like a good thing. And wipe old Bluetooth pairings too?
There was a problem hiding this comment.
I can think of a scenario where a designer wants only a single bootmagic key, in which case you'd want it to do everything, including clearing all settings and jumping to bootloader.
| @@ -0,0 +1,23 @@ | |||
| # Copyright (c) 2023, The ZMK Contributors | |||
There was a problem hiding this comment.
| # Copyright (c) 2023, The ZMK Contributors | |
| # Copyright (c) 2026, The ZMK Contributors |
|
As I've thought on this more, I'm having some concerns about having "single key" magic keys.. unlike QMK, we are far more likely to have someone tapping a key to wake the device and run this early code, potentially with them tapping/accidentally tapping the 0th key to do so. If we keep this as is, I fear we'll get a lot of accidental triggers. What do folks think about having multiple positions assignable to a given boot magic key? Doesn't need to have fancy combo handling, just enough to track "are all positions pressed that are part of this boot magic key?" |
That's a valid concern but since we aren't enabling this by default, the users should be aware of the specific key position (or the keyboard designers should tell the users), right? I think it's good to have, rather than must. What would the effective combo term be? Is it a simple trigger where you want both keys pressed at the time of the check (so the effective term is the boot time) or do we implement logic to wait for keys? |
Still might have something bump the corner of your keeb and wake it and since the check is just "is this key pressed before the timeout" you'd suddenly potentially lose all your studio customization, etc. I think this functionality is super useful, but we also shoukd try to design this in a way that's "easy not to screw up". I also don't trust designers/vendors to consistently communicate this, nor users to always read what is communicated by their vendor.
I think just "are all keys held at once before the boot magic timeout" |
|
I wonder if "check on boot" is even the right thing to aim for, for wireless devices. I think a "check on usb insertion" might make more sense for those kind of devices. We would lose more function for any keyboards wanting to run hardware without usb support, but those sorts of devices need to be flashed with a programmer and are unlikely to have a bootloader/are more heavy enthusiast. |
I like where you're thinking! Why don't we use the hwinfo API to determine the reset reason, and ignore the boot magic unless it's one of the "valid" reasons (.e.g. reset pin or power-on-reset)? I think that might a good way to avoid the accidental triggers from a key press to wake, and allow simple one button boot magic. |
|
Adding support for combos probably wouldn't be too hard either. You could add a |
Yeah, my thoughts exactly. We don't need crazy "combo" logic here. |
|
I think it would be a good idea to predefine a bootloader boot magic key for our in-tree keyboards. Maybe top left key as a convention, mirrored for splits? |
| Key positions are affected by the [matrix transform](../config/kscan.md#matrix-transform), so if your keyboard has multiple transforms for alternate layouts, you may need to adjust positions according to the user's selected transform. There is no automatic way to do this, but one way to simplify things for users is to add a block of commented out code to the keymap which selects the transform and updates the key positions to match if uncommented. | ||
|
|
||
| For example, consider a split keyboard which has 6 columns per side by default but supports a 5-column layout, and assume you want the top-left key on the left side and the top-right key on the right side to be boot magic keys. The top-left key will be position 0 regardless of layout, but the top-right key will be position 11 by default and position 9 in the 5-column layout. | ||
| For example, consider a split keyboard which has 6 columns per side by default but supports a 5-column layout, and assume you want the top-left key on the left side and the top-right key on the right side to be boot magic combos. The top-left key will be position 0 regardless of layout, but the top-right key will be position 11 by default and position 9 in the 5-column layout. |
There was a problem hiding this comment.
This will be an issue when the selected physical layout is changed at runtime (e.g. via ZMK Studio. I think we need a way to specify this per physical layout. @petejohanson do you have a suggestion?
There was a problem hiding this comment.
Yeah, I originally wrote this before physical layouts were a thing, so I didn't have any consideration for dynamically switching layouts.
How is this handled for sideband behaviors?
Actually, could we maybe just use sideband behaviors instead of creating a new system for this? Could we add properties to turn a sideband behavior into a boot magic key by making it only process the sideband behavior at boot and then pass through to the regular handling after that? Supporting combos with sideband behaviors would be tricky, but I don't know if that's a hard requirement.
There was a problem hiding this comment.
Sideband behaviors (typically) use their own kscan, right? So it doesn't relate to physical layouts. But I might not be thinking about this right.
I was thinking perhaps multiple bootmagic nodes defined, then each physical layout gets a phandle, similar to matrix transform/kscan. Then only the bootmagic node referred to by the active physical transform is active.
|
I could use some more clarity on how to handle physical layouts here. Should the bootmagic key/combo be defined independently for each physical layout? Or defined in terms of the kscan row and column? Or keep the behavior as-is? |
I was referring to the issues @joelspadin and @caksoylar brought up in their discussion above, but your problem does sound relevant. Even though we are working at different layers, the user-facing configuration for combos and bootmagic should ideally be very similar. |
|
I've revised the configuration of the boot magic combos to happen at the physical layout layer instead of the global layer as @caksoylar has suggested. I think overall this is the cleanest approach, and it also makes working with split keyboards nicer. Here's how the example in the docs for boot magic key configuration on a split keyboard with two physical layouts looks: shield.dtsi / {
chosen {
zmk,physical-layout = &default_layout;
};
default_layout: default_layout {
compatible = "zmk,physical-layout";
display-name = "Default Layout";
transform = <&default_transform>;
};
five_column_layout: five_column_layout {
compatible = "zmk,physical-layout";
display-name = "5-Column Layout";
transform = <&five_column_transform>;
};
bootloader_key_left: bootloader_key_left {
compatible = "zmk,boot-magic-combo";
combo-positions = <0>;
jump-to-bootloader;
};
bootloader_key_right: bootloader_key_right {
compatible = "zmk,boot-magic-combo";
combo-positions = <11>;
jump-to-bootloader;
};
bootloader_key_right_fivecol: bootloader_key_right_fivecol {
compatible = "zmk,boot-magic-combo";
combo-positions = <9>;
jump-to-bootloader;
};
...
};shield_left.overlay #include "shield.dtsi"
&default_layout {
boot-magic-combos = <&bootloader_key_left>;
};
&five_column_layout {
boot-magic-combos = <&bootloader_key_left>;
}shield_right.overlay #include "shield.dtsi"
&default_layout {
boot-magic-combos = <&bootloader_key_right>;
};
&five_column_layout {
boot-magic-combos = <&bootloader_key_right_fivecol>;
}shield.keymap // Uncomment this block if using the 5-column layout
// / {
// chosen {
// zmk,physical-layout = &five_column_layout;
// };
// }; |
I've built on #1757 to merge in the latest changes in ZMK. Most notably I've integrated the new boot retention mode settings and addressed the comments in the previous PR.
PR check-list